//========================================================= 
/**@file  NesMapper_1.h
 * @brief  
 * 
 * @date 2024-07-30 14:52:21 
 * @author yingbaizhou@tencent.com 
 * @version 1.0
 */ 
//--------------------------------------------------------- 
#ifndef _LIBZHOUYB_NESMAPPER_1_H_
#define _LIBZHOUYB_NESMAPPER_1_H_
//--------------------------------------------------------- 
#include "../INesInterface.h"
#include "../NesMapper.h"
//--------------------------------------------------------- 
namespace zhou_yb {
namespace wrapper {
namespace nes {
//--------------------------------------------------------- 
class NesMapper_1 : public INesMapper, public IChrMapperRouter, public IPrgMapperRouter, public RefObject
{
public:
    union MMC1_Control {
        struct
        {
            byte mirror_mode : 2;
            byte prg_bank_mode : 2;
            byte chr_bank_mode : 1;
        };
        byte value;
    };
protected:
    byte _load_register = 0;
    byte _load_register_count = 0;
    MMC1_Control _control_register;

    byte _chr_bank_4_lo = 0;
    byte _chr_bank_4_hi = 0;
    byte _chr_bank_8 = 0;

    byte _chr_last_bank_lo = 0;
    byte _chr_last_bank_hi = 0;

    byte _prg_bank_16_lo = 0;
    byte _prg_bank_16_hi = 0;
    byte _prg_bank_32 = 0;

    ByteArray _chr_buff;
    ByteArray _prg_buff;

    uint _prg_banks = 0;

    ChrRouterAdapter _chr;
    PrgRouterAdapter _prg;

    INesMapperObserver* _observer = nullptr;
public:
    virtual bool Set(const NesROM& rom)
    {
        _chr.Set(*this);
        _prg.Set(*this);

        _chr_buff = rom.chr;
        _prg_buff = rom.prg;

        _prg_banks = rom.prg_count;

        Reset();
        return true;
    }
    virtual void SetObserver(INesMapperObserver& observer)
    {
        _observer = &observer;
    }

    virtual IMapper* GetChrMapper() { return &_chr; }
    virtual IMapper* GetPrgMapper() { return &_prg; }

    virtual void ChrWrite(ushort addr, byte value) {}
    virtual byte ChrRead(ushort addr)
    {
        uint chr_addr = addr;
        if (_control_register.chr_bank_mode)
        {
            if (addr <= 0x0FFF)
            {
                chr_addr = _chr_bank_4_lo * 0x1000 + addr;
            }
            else
            {
                chr_addr = _chr_bank_4_hi * 0x1000 + (addr & 0x0FFF);
            }
        }
        else
        {
            chr_addr = _chr_bank_8 * 0x2000 + (addr & 0x1FFF);
        }
        return _chr_buff[chr_addr];
    }

    virtual void PrgWrite(ushort addr, byte value)
    {
        const NesMIRROR mirror_mode[4] = { NesMIRROR::ONESCREEN_LO, NesMIRROR::ONESCREEN_HI, NesMIRROR::VERTICAL, NesMIRROR::HORIZONTAL };

#define CHR_BANK_UPDATE(bank, x, bank_mask) if (bank != (x)) { bank = (x); if (_observer) { _observer->OnChrUpdate(bank_mask); } }

        if (value & 0x80)
        {
            _load_register = 0;
            _load_register_count = 0;
            _control_register.value |= 0x0C;
            return;
        }
        if (value & 0x01)
        {
            _load_register |= (0x01 << _load_register_count);
        }
        ++_load_register_count;
        if (_load_register_count < 5)
            return;
        // $8000 - $9FFF
        if (addr <= 0x1FFF)
        {
            _control_register.value = _load_register;
            if (_observer)
            {
                _observer->OnMirrorUpdate(mirror_mode[_control_register.mirror_mode]);
            }
        }
        // $A000 - $BFFF
        else if (addr <= 0x3FFF)
        {
            // 4K Mode
            if (_control_register.chr_bank_mode)
            {
                CHR_BANK_UPDATE(_chr_bank_4_lo, _load_register & 0x1F, 0x01);
            }
            else
            {
                CHR_BANK_UPDATE(_chr_bank_8, _load_register & 0x1E, 0x03);
            }
        }
        // $C000 - $DFFF
        else if (addr <= 0x5FFF)
        {
            if (_control_register.chr_bank_mode)
            {
                CHR_BANK_UPDATE(_chr_bank_4_hi, _load_register & 0x1F, 0x02);
            }
        }
        // $E000 - $FFFF
        else if (addr <= 0x7FFF)
        {
            switch (_control_register.prg_bank_mode)
            {
            case 0:
            case 1:
                _prg_bank_32 = (_load_register & 0x0E) >> 1;
                break;
            case 2:
                _prg_bank_16_lo = 0;
                _prg_bank_16_hi = _load_register & 0x0F;
                break;
            case 3:
                _prg_bank_16_lo = _load_register & 0x0F;
                _prg_bank_16_hi = _prg_banks - 1;
                break;
            }
        }
        _load_register = 0;
        _load_register_count = 0;
    }
    virtual byte PrgRead(ushort addr)
    {
        uint prg_addr = addr;
        // 0,1:32K Mode  2,3:16K Mode
        if (_control_register.prg_bank_mode > 1)
        {
            if (addr <= 0x3FFF)
            {
                prg_addr = _prg_bank_16_lo * 0x4000 + addr;
            }
            else
            {
                prg_addr = _prg_bank_16_hi * 0x4000 + (addr & 0x3FFF);
            }
        }
        else
        {
            prg_addr = _prg_bank_32 * 0x8000 + (addr & 0x7FFF);
        }
        return _prg_buff[prg_addr];
    }

    virtual void Reset()
    {
        _load_register = 0;
        _load_register_count = 0;
        _control_register.value = 0x1C;

        _chr_bank_4_hi = 0;
        _chr_bank_4_lo = 0;
        _chr_bank_8 = 0;

        _prg_bank_16_hi = _prg_banks - 1;
        _prg_bank_16_lo = 0;
        _prg_bank_32 = 0;
    }
    virtual void Frame(size_t frame_count)
    {
        if (_observer)
        {
            byte chr_mask = 0x00;
            if (_control_register.chr_bank_mode)
            {
                if (_chr_bank_4_lo != _chr_last_bank_lo)
                {
                    LOGGER(_log << "Frame:" << frame_count << " ChrBankLo:" 
                        << _hex(_chr_last_bank_lo) << "->" << _hex(_chr_bank_4_lo) << endl);

                    chr_mask |= 0x01;
                    _chr_last_bank_lo = _chr_bank_4_lo;
                }
                if (_chr_bank_4_hi != _chr_last_bank_hi)
                {
                    LOGGER(_log << "Frame:" << frame_count << " ChrBankHi:" 
                        << _hex(_chr_last_bank_hi) << "->" << _hex(_chr_bank_4_hi) << endl);

                    chr_mask |= 0x02;
                    _chr_last_bank_hi = _chr_bank_4_hi;
                }
            }
            else
            {
                if (_chr_bank_8 != _chr_last_bank_hi)
                {
                    LOGGER(_log << "Frame:" << frame_count << " ChrBank:" 
                        << _hex(_chr_last_bank_hi) << "->" << _hex(_chr_bank_8) << endl);

                    chr_mask = 0x03;
                    _chr_last_bank_hi = _chr_bank_8;
                }
            }
            if (chr_mask)
            {
                _observer->OnChrUpdate(chr_mask);
            }
        }
    }
};
//--------------------------------------------------------- 
} // namespace nes
} // namespace wrapper
} // namespace zhou_yb
//---------------------------------------------------------
#endif // _LIBZHOUYB_NESMAPPER_1_H_
//=========================================================